package simulation;

import framework.Force;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.Scanner;


/**
 * Class to handle all of the global forces, keeps simulation cleaner.
 * 
 * @author David Howdyshell, Zack Hopping
 * 
 */
public class GlobalForceHandler {
    // Definitions
    private static final int TOP_WALL = 1;
    private static final int RIGHT_WALL = 2;
    private static final int LEFT_WALL = 3;
    private static final int BOTTOM_WALL = 4;
    private static final Force ZERO_FORCE = new Force(0, 0);
    private static final int ANGLE_RIGHT = 0;
    private static final int ANGLE_DOWN = 90;
    private static final int ANGLE_LEFT = 180;
    private static final int ANGLE_UP = 270;

    // the simulation...
    private Simulation mySim;

    // State of forces
    private Force myGravity;
    private Force myViscosity;
    private Force myCenterMassForce;
    private HashMap<Integer, Force> myWallForces;
    private HashMap<Integer, Boolean> myToggleForces;

    /**
     * Initializes all forces to their default values.
     * 
     * @param sim - pointer to the simulation
     */
    public GlobalForceHandler (Simulation sim) {
        mySim = sim;
        myGravity = new Force(ANGLE_DOWN, 1);
        myViscosity = new Force(0, 0);
        myCenterMassForce = new Force(0, 0);
        myWallForces = new HashMap<Integer, Force>();
        myToggleForces = new HashMap<Integer, Boolean>();
        initToggleForces();
    }

    /**
     * Initializes all forces to be turned on by default.
     */
    private void initToggleForces () {
        int[] keys = { KeyEvent.VK_G, KeyEvent.VK_V, KeyEvent.VK_M,
                KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4 };
        for (int key : keys) {
            myToggleForces.put(key, true);
        }
    }

    /**
     * Toggles force on/off based on user input.
     * 
     * @param key - user pressed key.
     */
    public void setToggleForce (int key) {
        if (myToggleForces.containsKey(key)) {
            myToggleForces.put(key, !myToggleForces.get(key));
        }
    }

    /**
     * Sets gravity from input file line.
     * 
     * @param line - text line from input.
     */
    public void setGravity (Scanner line) {
        double direction = line.nextDouble();
        double magnitude = line.nextDouble();
        myGravity = new Force(direction, magnitude);
    }

    /**
     * Sets viscosity from input file line.
     * 
     * @param line - text line from input.
     */
    public void setViscosity (Scanner line) {
        double magnitude = line.nextDouble();
        myViscosity = new Force(0, magnitude);
    }

    /**
     * Sets CoM force values from input file line.
     * 
     * @param line - text line from input.
     */
    public void setCenterMass (Scanner line) {
        double magnitude = line.nextDouble();
        double scale = line.nextDouble();
        myCenterMassForce = new Force(scale, magnitude);
    }

    /**
     * Sets wall force from input file line.
     * 
     * @param line - text line from input.
     */
    public void setWallForce (Scanner line) {
        int whichWall = line.nextInt();
        double magnitude = line.nextDouble();
        double exponent = line.nextDouble();
        myWallForces.put(whichWall, new Force(exponent, magnitude));
    }

    /**
     * Gets gravitational force.
     * 
     * @param mass - mass object to calc gravity for
     * @return - force of gravity on the mass
     */
    public Force getGravity (double mass) {
        if (myToggleForces.get(KeyEvent.VK_G)) {
            Force result = myGravity;
            result.scale(1.0 / mass);
            return result;
        }
        return ZERO_FORCE;
    }

    /**
     * Gets values related to center of mass force.
     * 
     * @return - force object storing the values from input file
     */
    public Force getCenterMassForce () {
        if (myToggleForces.get(KeyEvent.VK_M)) { return myCenterMassForce; }
        return ZERO_FORCE;
    }

    /**
     * Get drag force on mass.
     * 
     * @param velocity - velocity of mass on which to apply force
     * @return force due to "air" viscosity
     */
    public Force getViscosityForce (Force velocity) {
        if (myToggleForces.get(KeyEvent.VK_V)) {
            Force result = new Force(velocity);
            result.scale(myViscosity.getMagnitude());
            result.negate();
            return result;
        }
        return ZERO_FORCE;
    }

    /**
     * Sums the forces applied by all four walls on the mass.
     * 
     * @param location - location of the mass
     * @return net force from all 4 walls
     */
    public Force getNetWallForce (Point2D location) {
        Force result = new Force(0, 0);
        if (myToggleForces.get(KeyEvent.VK_1)) {
            result.sum(getWallForce(TOP_WALL, location));
        }
        if (myToggleForces.get(KeyEvent.VK_3)) {
            result.sum(getWallForce(BOTTOM_WALL, location));
        }
        if (myToggleForces.get(KeyEvent.VK_4)) {
            result.sum(getWallForce(LEFT_WALL, location));
        }
        if (myToggleForces.get(KeyEvent.VK_2)) {
            result.sum(getWallForce(RIGHT_WALL, location));
        }
        return result;
    }

    /**
     * Returns the force applied by the specified wall on mass at given
     * location.
     * 
     * @param wallIndex - integer index of desired wall
     * @param location - location of mass for which to calc force
     * @return force applied by specified wall
     */
    private Force getWallForce (int wallIndex, Point2D location) {
        if (myWallForces.containsKey(wallIndex)) {
            Force f = myWallForces.get(wallIndex);
            double direction = 0;
            double distance = 0;
            if (wallIndex == TOP_WALL) {
                direction = ANGLE_UP;
                distance = mySim.getSizeModifier() + location.getY();
            }
            else if (wallIndex == BOTTOM_WALL) {
                direction = ANGLE_DOWN;
                distance = (mySim.getSize().getHeight() + mySim
                        .getSizeModifier())
                        - location.getY();
            }
            else if (wallIndex == LEFT_WALL) {
                direction = ANGLE_LEFT;
                distance = location.getX() + mySim.getSizeModifier();
            }
            else if (wallIndex == RIGHT_WALL) {
                direction = ANGLE_RIGHT;
                distance = (mySim.getSize().getWidth() + mySim
                        .getSizeModifier())
                        - location.getX();
            }
            Force result = new Force(direction, f.getMagnitude());
            result.scale(1.0 / Math.pow(distance, Math.abs(f.getDirection())));
            result.negate();
            return result;
        }
        return ZERO_FORCE;

    }
}
